//========================================================= 
/**@file NesMapper_4.h
* @brief
*
* @date 2024-07-21   14:48:13
* @author Zhyioo
* @version 1.0
*/
//--------------------------------------------------------- 
#ifndef _LIBZHOUYB_NESMAPPER_4_H_
#define _LIBZHOUYB_NESMAPPER_4_H_
//--------------------------------------------------------- 
#include "../INesInterface.h"
#include "../NesMapper.h"
//--------------------------------------------------------- 
namespace zhou_yb {
namespace wrapper {
namespace nes {
//--------------------------------------------------------- 
class NesMapper_4 : public NesSwitchableMapper
{
public:
    union MMC3_BankSelect
    {
        struct
        {
            byte data_register : 3;
            byte unused : 2;
            byte m : 1;
            // 0: $8000-$9FFF swappable,$C000 - $DFFF fixed to second - last bank
            // 1: $C000-$DFFF swappable,$8000 - $9FFF fixed to second - last bank
            byte prg_bank_mode : 1;
            // 0: two 2 KB banks at $0000-$0FFF,four 1 KB banks at $1000 - $1FFF
            // 1: two 2 KB banks at $1000-$1FFF,four 1 KB banks at $0000 - $0FFF
            byte chr_bank_mode : 1;
        } selection;
        byte value;
    };
protected:
    MMC3_BankSelect _bank_select;
    byte _bank_value[8];

    uint _prg_bank_addr[4];
    uint _chr_bank_addr[8];

    bool _is_irq_enable = false;
    byte _irq_reload = 0;
    byte _irq_count = 0;

    void _DisplayCHR()
    {
        LOGGER(const char* chr_d7_0 = "R0\0  \0R1\0  \0R2\0R3\0R4\0R5\0";
        const char* chr_d7_1 = "R2\0R3\0R4\0R5\0R0\0  \0R1\0  \0";
        const char* chr_desc = _bank_select.selection.chr_bank_mode ? chr_d7_1 : chr_d7_0;
        for (int i = 0; i < 8; ++i)
        {
            ByteArray chr = _chr_buff.SubArray(_chr_bank_addr[i], CHR_BANK_SIZE);
            const char* chr_bank_desc = chr_desc + (3 * i);
            _log << "CHR[" << i << "] " << (chr_desc + 3 * i);
            if (chr_bank_desc[1] == ' ')
                _log << "     ";
            else
                _log << " (" << _hex(_bank_value[chr_bank_desc[1] - '0']) << ")";
            _log << " $" << _hex(i * CHR_BANK_SIZE, 2) << "-$" << _hex(0x3FF + i * CHR_BANK_SIZE, 2) << "=" << _hex(_chr_bank_addr[i])
                << " " << _hex(chr.SubArray(0, 8)) << "..." << _hex(ByteConvert::Last(chr, 8)) << endl;
        })
    }
    void _DisplayPRG()
    {
        LOGGER(const char* prg_d6_0 = "R6\0R7\0-2\0-1\0";
        const char* prg_d6_1 = "-2\0R7\0R6\0-1\0";
        const char* prg_desc = _bank_select.selection.chr_bank_mode ? prg_d6_1 : prg_d6_0;
        for (int i = 0; i < 4; ++i)
        {
            ByteArray prg = _prg_buff.SubArray(_prg_bank_addr[i], PRG_BANK_SIZE);
            const char* prg_bank_desc = prg_desc + (3 * i);
            _log << "PRG[" << i << "] " << (prg_desc + 3 * i);
            if (prg_bank_desc[0] == '-')
                _log << "     ";
            else
                _log << " (" << _hex(_bank_value[prg_bank_desc[1] - '0']) << ")";
            _log << " $" << _hex(0x8000 + i * PRG_BANK_SIZE, 2) << "-$" << _hex(0x9FFF + i * PRG_BANK_SIZE, 2) << "=" << _hex(_prg_bank_addr[i])
                << " " << _hex(prg.SubArray(0, 8)) << "..." << _hex(ByteConvert::Last(prg, 8)) << endl;
        })
    }
public:
    virtual byte ChrRead(ushort addr)
    {
        uint bank = addr >> 10;
        uint bank_addr = _chr_bank_addr[bank] + (addr & 0x3FF);
        return _chr_buff[bank_addr];
    }
    virtual byte PrgRead(ushort addr)
    {
        uint bank = addr >> 13;
        uint bank_addr = _prg_bank_addr[bank] + (addr & 0x1FFF);
        return _prg_buff[bank_addr];
    }
    virtual void PrgWrite(ushort addr, byte value)
    {
        // 0x8000 - 0x9FFF
        if (addr <= 0x1FFF)
        {
            if (addr & 0x01)
            {
                uint selected_bank = _bank_select.selection.data_register;
                // R6 and R7 will ignore the top two bits, as the MMC3 has only 6 PRG ROM address lines
                if (selected_bank == 6 || selected_bank == 7)
                {
                    value &= 0x3F;
                }
                // R0 and R1 ignore the bottom bit
                else if (selected_bank == 0 || selected_bank == 1)
                {
                    value &= 0xFE;
                }
                _bank_value[selected_bank] = value;
                LOGGER(_log << "==> R" << selected_bank << "=" << _hex(value) << "(" << _hex(value) << ")" << endl);
                if (selected_bank < 6)
                {
                    if (_bank_select.selection.chr_bank_mode)
                    {
                        _chr_bank_addr[0] = _bank_value[2] * CHR_BANK_SIZE;
                        _chr_bank_addr[1] = _bank_value[3] * CHR_BANK_SIZE;
                        _chr_bank_addr[2] = _bank_value[4] * CHR_BANK_SIZE;
                        _chr_bank_addr[3] = _bank_value[5] * CHR_BANK_SIZE;
                        _chr_bank_addr[4] = _bank_value[0] * CHR_BANK_SIZE;
                        _chr_bank_addr[5] = _bank_value[0] * CHR_BANK_SIZE + CHR_BANK_SIZE;
                        _chr_bank_addr[6] = _bank_value[1] * CHR_BANK_SIZE;
                        _chr_bank_addr[7] = _bank_value[1] * CHR_BANK_SIZE + CHR_BANK_SIZE;
                    }
                    else
                    {
                        _chr_bank_addr[0] = _bank_value[0] * CHR_BANK_SIZE;
                        _chr_bank_addr[1] = _bank_value[0] * CHR_BANK_SIZE + CHR_BANK_SIZE;
                        _chr_bank_addr[2] = _bank_value[1] * CHR_BANK_SIZE;
                        _chr_bank_addr[3] = _bank_value[1] * CHR_BANK_SIZE + CHR_BANK_SIZE;
                        _chr_bank_addr[4] = _bank_value[2] * CHR_BANK_SIZE;
                        _chr_bank_addr[5] = _bank_value[3] * CHR_BANK_SIZE;
                        _chr_bank_addr[6] = _bank_value[4] * CHR_BANK_SIZE;
                        _chr_bank_addr[7] = _bank_value[5] * CHR_BANK_SIZE;
                    }
                    if (_observer)
                    {
                        _observer->OnChrUpdate(0x03);
                    }
                    LOGGER(_DisplayCHR());
                }
                else
                {
                    if (_bank_select.selection.prg_bank_mode)
                    {
                        _prg_bank_addr[0] = _prg_buff.GetLength() - 2 * PRG_BANK_SIZE;
                        _prg_bank_addr[1] = _bank_value[7] * PRG_BANK_SIZE;
                        _prg_bank_addr[2] = _bank_value[6] * PRG_BANK_SIZE;
                        _prg_bank_addr[3] = _prg_buff.GetLength() - 1 * PRG_BANK_SIZE;
                    }
                    else
                    {
                        _prg_bank_addr[0] = _bank_value[6] * PRG_BANK_SIZE;
                        _prg_bank_addr[1] = _bank_value[7] * PRG_BANK_SIZE;
                        _prg_bank_addr[2] = _prg_buff.GetLength() - 2 * PRG_BANK_SIZE;
                        _prg_bank_addr[3] = _prg_buff.GetLength() - 1 * PRG_BANK_SIZE;
                    }
                    LOGGER(_DisplayPRG());
                }
            }
            else
            {
                _bank_select.value = value;

                LOGGER(_log << "Bank select:" << int(_bank_select.selection.data_register) << '=' << _hex(value)
                    << " ChrMode:" << int(_bank_select.selection.chr_bank_mode)
                    << " PrgMode:" << int(_bank_select.selection.prg_bank_mode)
                    << endl);
            }
            return;
        }
        // 0xA000 - 0xBFFF
        if (addr >= 0x2000 && addr <= 0x3FFF)
        {
            if (addr & 0x01)
            {
                // PRG Protect
            }
            else
            {
                LOGGER(_log << "Mirror:" << ((value & 0x01) ? "HORIZONTAL" : "VERTICAL") << endl);
                if (_observer)
                {
                    _observer->OnMirrorUpdate(value & 0x01 ? NesMIRROR::HORIZONTAL : NesMIRROR::VERTICAL);
                }
            }
            return;
        }
        // 0xC000 - 0xDFFF
        if (addr >= 0x4000 && addr <= 0x5FFF)
        {
            if (addr & 0x01)
            {
                LOGGER(_log.WriteLine("IRQ clear"));
                _irq_count = 0;
            }
            else
            {
                LOGGER(_log << "IRQ reload:" << int(value) << endl);
                _irq_reload = value;
            }
            return;
        }
        // 0xE000 - 0xFFFF
        if (addr >= 0x6000 && addr <= 0x7FFF)
        {
            if (addr & 0x01)
            {
                LOGGER(_log.WriteLine("IRQ enable"));
                _is_irq_enable = true;
            }
            else
            {
                LOGGER(_log.WriteLine("IRQ disable"));
                _is_irq_enable = false;
            }
            return;
        }
    }

    virtual void Reset()
    {
        _bank_select.value = 0;

        _bank_value[0] = 0;
        _bank_value[1] = 2;
        _bank_value[2] = 4;
        _bank_value[3] = 5;
        _bank_value[4] = 6;
        _bank_value[5] = 7;
        _bank_value[6] = 0;
        _bank_value[7] = 1;

        for (int i = 0; i < 8; ++i)
            _chr_bank_addr[i] = i * CHR_BANK_SIZE;

        _prg_bank_addr[0] = 0x0000;
        _prg_bank_addr[1] = PRG_BANK_SIZE;
        _prg_bank_addr[2] = _prg_buff.GetLength() - 2 * PRG_BANK_SIZE;
        _prg_bank_addr[3] = _prg_buff.GetLength() - 1 * PRG_BANK_SIZE;

        LOGGER(_DisplayCHR());
        LOGGER(_DisplayPRG());
    }
    virtual void Scanline(uint scanline)
    {
        if (_irq_count < 1)
        {
            _irq_count = _irq_reload;
        }
        else
        {
            --_irq_count;
        }
        if (_irq_count == 0 && _is_irq_enable && _observer)
        {
            _observer->OnIrqRequest();
        }
    }
};
//--------------------------------------------------------- 
} // namespace nes
} // namespace wrapper
} // namespace zhou_yb
//--------------------------------------------------------- 
#endif // _LIBZHOUYB_NESMAPPER_4_H_
//========================================================= 